/*
 * GooglePlaces.java
 * Jan 20, 2013 
 */
package com.jramoyo.util;

import java.util.Collections;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;

import javax.inject.Inject;

import org.codehaus.jackson.annotate.JsonIgnoreProperties;
import org.codehaus.jackson.annotate.JsonProperty;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.client.RestTemplate;

import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;

/**
 * @author jramoyo
 */
public class GooglePlaces {

	private static final Logger logger = LoggerFactory.getLogger(GooglePlaces.class);

	private static final String PLACE_DETAILS_URL = "https://maps.googleapis.com/maps/api/place/details/json?reference={searchId}&sensor=false&key={key}";

	@Value("${api.key}")
	private String apiKey;

	@Inject
	private RestTemplate restTemplate;

	private LoadingCache<String, PlaceDetails> placeDetails = CacheBuilder.newBuilder().maximumSize(1000).expireAfterAccess(24, TimeUnit.HOURS)
			.build(new CacheLoader<String, PlaceDetails>() {
				public PlaceDetails load(String searchId) throws Exception {
					PlaceDetailsResponse response = restTemplate.getForObject(PLACE_DETAILS_URL, PlaceDetailsResponse.class, searchId, apiKey);
					if (response.getResult() != null) {
						return response.getResult();
					} else {
						throw new PlacesException("Unable to find details for reference: " + searchId);
					}
				}
			});

	/**
	 * Fetches the details of a place identified by a reference (searchId)
	 * 
	 * @param searchId
	 *            the place reference identifier
	 * @return the details of a place identified by a reference (searchId)
	 */
	public PlaceDetails getPlaceDetails(String searchId) {
		try {
			return placeDetails.get(searchId);
		} catch (ExecutionException e) {
			logger.warn("An exception occurred while fetching place details!", e.getCause());
			return null;
		}
	}

	@JsonIgnoreProperties(ignoreUnknown = true)
	public static class PlaceDetails {

		@JsonProperty("name")
		private String name;

		@JsonProperty("icon")
		private String icon;

		@JsonProperty("url")
		private String url;

		@JsonProperty("formatted_address")
		private String address;

		@JsonProperty("geometry")
		private PlaceGeometry geometry;

		@JsonProperty("photos")
		private List<PlacePhoto> photos = Collections.emptyList();

		public String getAddress() {
			return address;
		}

		public PlaceGeometry getGeometry() {
			return geometry;
		}

		public String getIcon() {
			return icon;
		}

		public String getName() {
			return name;
		}

		public List<PlacePhoto> getPhotos() {
			return photos;
		}

		public String getUrl() {
			return url;
		}

		public void setAddress(String address) {
			this.address = address;
		}

		public void setGeometry(PlaceGeometry geometry) {
			this.geometry = geometry;
		}

		public void setIcon(String icon) {
			this.icon = icon;
		}

		public void setName(String name) {
			this.name = name;
		}

		public void setPhotos(List<PlacePhoto> photos) {
			this.photos = photos;
		}

		public void setUrl(String url) {
			this.url = url;
		}
	}

	@JsonIgnoreProperties(ignoreUnknown = true)
	public static class PlaceDetailsResponse {
		@JsonProperty("result")
		private PlaceDetails result;

		public PlaceDetails getResult() {
			return result;
		}

		public void setResult(PlaceDetails result) {
			this.result = result;
		}
	}

	@JsonIgnoreProperties(ignoreUnknown = true)
	public static class PlaceGeometry {

		@JsonProperty("location")
		private PlaceLocation location;

		public PlaceLocation getLocation() {
			return location;
		}

		public void setLocation(PlaceLocation location) {
			this.location = location;
		}
	}

	@JsonIgnoreProperties(ignoreUnknown = true)
	public static class PlaceLocation {

		@JsonProperty("lat")
		private String lat;

		@JsonProperty("lng")
		private String lng;

		public String getLat() {
			return lat;
		}

		public String getLng() {
			return lng;
		}

		public void setLat(String lat) {
			this.lat = lat;
		}

		public void setLng(String lng) {
			this.lng = lng;
		}
	}

	@JsonIgnoreProperties(ignoreUnknown = true)
	public static class PlacePhoto {

		@JsonProperty("photo_reference")
		private String reference;

		public String getReference() {
			return reference;
		}

		public void setReference(String reference) {
			this.reference = reference;
		}
	}

	public static class PlacesException extends Exception {

		private static final long serialVersionUID = 433629765655711368L;

		public PlacesException(String message) {
			super(message);
		}

		public PlacesException(String message, Throwable cause) {
			super(message, cause);
		}
	}
}